import sys
from io import BytesIO
from os.path import basename, join, pardir, abspath
from os import system, makedirs

import numpy
from PyQt6.QtCore import Qt, QSize, QPointF
from PyQt6.QtGui import QIcon, QPixmap, QFont

from dialog.TextMarkDialog import TextMarkDialog
from ui.PicShop_ui import Ui_MainWindow

from PyQt6.QtWidgets import QApplication, QMainWindow, QFileDialog, QPushButton, QMessageBox, QLabel, QSlider, QComboBox

from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageFilter

from dialog.RatioDialog import RatioDialog
from widget.PictureLabel import PictureLabel

import cv2

# 区域选择模式
regionMode = ''
# 当前打开的图片地址
picPath = ''
# 第二张图片地址
picPath2 = ''


class MyPicShop(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MyPicShop, self).__init__()
        self.setupUi(self)
        self.setWindowFlag(Qt.WindowType.MSWindowsFixedSizeDialogHint)
        self.setWindowIcon(QIcon('image/picshop.jpg'))
        # 自定义添加的控件
        # 图片显示相关
        self.lbPic = PictureLabel(self.actPaste, self.centralwidget, self)
        self.lbPic.mousemoved.connect(self.update_status)
        self.lbPic2 = QLabel(self.centralwidget)
        self.lbPic2.setScaledContents(True)
        self.lbPic2.setVisible(False)
        # 合成模式
        self.cobCompose = QComboBox()
        self.pbCompose = QPushButton()
        # 其他属性
        # 鼠标位置
        self.p = QPointF(0, 0)
        # 复制图片
        self.copyImage: Image.Image | None = None
        # 图片增强
        self.enhanceImage: Image.Image | None = None
        self.enhanceEnabled = True
        self.enhanceType = ''
        # 显示图片
        self.pImage: Image.Image | None = None
        self.width = 0
        self.height = 0
        self.scale = 1  # 缩放比
        # 复制图片
        self.cImage: Image.Image | None = None
        # 人脸
        self.fImage: Image.Image | None = None
        self.faces = [tuple[float, float, float]]  # (x, y, r)
        self.faceProcess = 0
        # 初始化
        self.setup_actions()
        self.setup_tools()

    def setup_actions(self):
        """
        初始化 QAction
        """
        # 文件
        self.actOpen.setShortcut('Ctrl+O')
        self.actOpen.setIcon(QIcon('image/open.jpg'))
        self.actOpen.triggered.connect(self.open_pic)
        self.actSave.setShortcut('Ctrl+S')
        self.actSave.setIcon(QIcon('image/save.jpg'))
        self.actSave.triggered.connect(self.save_pic)
        self.actSaveAs.setShortcut('Ctrl+Shift+S')
        self.actSaveAs.triggered.connect(self.save_as_pic)
        self.actQuit.triggered.connect(lambda: QApplication.instance().quit())
        # 编辑
        self.actCircle.triggered.connect(lambda: self.toggle_region('circle'))
        self.actRect.triggered.connect(lambda: self.toggle_region('rect'))
        self.actCut.setShortcut('Ctrl+X')
        self.actCut.setIcon(QIcon('image/cut.jpg'))
        self.actCut.triggered.connect(self.cut)
        self.actCopy.setShortcut('Ctrl+C')
        self.actCopy.setIcon(QIcon('image/copy.jpg'))
        self.actCopy.triggered.connect(self.copy)
        self.actPaste.setShortcut('Ctrl+V')
        self.actPaste.setIcon(QIcon('image/paste.jpg'))
        self.actPaste.triggered.connect(self.paste)
        # 图像
        self.actBlackWhite.setIcon(QIcon('image/blackwhite.jpg'))
        self.actBlackWhite.triggered.connect(lambda: self.toggle_image_mode('L'))
        self.actMultiColour.setIcon(QIcon('image/multicolour.jpg'))
        self.actMultiColour.triggered.connect(lambda: self.toggle_image_mode('RGBA'))
        self.actRatio.triggered.connect(self.fit_ratio)
        self.actHorizontal.setIcon(QIcon('image/mirrorhorizontal.jpg'))
        self.actHorizontal.triggered.connect(lambda: self.transpose(Image.Transpose.FLIP_LEFT_RIGHT))
        self.actVertical.setIcon(QIcon('image/mirrorvertical.jpg'))
        self.actVertical.triggered.connect(lambda: self.transpose(Image.Transpose.FLIP_TOP_BOTTOM))
        self.actRotateLeft.setIcon(QIcon('image/rotateleft.jpg'))
        self.actRotateLeft.triggered.connect(lambda: self.rotate(10))
        self.actRotateRight.setIcon(QIcon('image/rotateright.jpg'))
        self.actRotateRight.triggered.connect(lambda: self.rotate(-10))
        self.actZoomIn.setIcon(QIcon('image/zoomin.jpg'))
        self.actZoomIn.triggered.connect(lambda: self.scale_pic(1.2))
        self.actZoomOut.setIcon(QIcon('image/zoomout.jpg'))
        self.actZoomOut.triggered.connect(lambda: self.scale_pic(0.8))
        self.actWaterMark.triggered.connect(self.add_water_mark)
        # 美化
        self.actContrast.triggered.connect(self.enhance_contrast)
        self.actColor.triggered.connect(self.enhance_color)
        self.actBright.triggered.connect(self.enhance_bright)
        self.actSharp.triggered.connect(self.enhance_sharp)
        self.actPanel.triggered.connect(
            lambda: self.tbrPanel.setVisible(not self.tbrPanel.isVisible()))
        self.actCompose.triggered.connect(self.compose_begin)
        self.actFace.setIcon(QIcon('image/face.jpg'))
        self.actFace.triggered.connect(self.face_filter)
        # 关于

    def setup_tools(self):
        """
        初始化工具栏
        """
        # 主工具栏
        self.tbrMain.layout().setContentsMargins(5, 5, 5, 5)
        self.tbrMain.layout().setSpacing(10)
        self.tbrMain.setIconSize(QSize(36, 36))
        # 打开 保存
        self.tbrMain.addAction(self.actOpen)
        self.tbrMain.addAction(self.actSave)
        self.tbrMain.addSeparator()
        # 选区 剪切 复制 粘贴
        self.pbSelRect = QPushButton()
        self.pbSelRect.setIcon(QIcon('image/rect.jpg'))
        self.pbSelRect.setToolTip('选择矩形选区')
        self.pbSelRect.setIconSize(QSize(36, 36))
        self.pbSelRect.setStyleSheet('background-color: whitesmoke;')
        self.pbSelRect.clicked.connect(lambda: self.toggle_region('rect'))
        self.tbrMain.addWidget(self.pbSelRect)
        self.pbSelCircle = QPushButton()
        self.pbSelCircle.setIcon(QIcon('image/circle.jpg'))
        self.pbSelCircle.setToolTip('选择圆形选区')
        self.pbSelCircle.setIconSize(QSize(36, 36))
        self.pbSelCircle.setStyleSheet('background-color: whitesmoke;')
        self.pbSelCircle.clicked.connect(lambda: self.toggle_region('circle'))
        self.tbrMain.addWidget(self.pbSelCircle)
        self.tbrMain.addAction(self.actCut)
        self.tbrMain.addAction(self.actCopy)
        self.tbrMain.addAction(self.actPaste)
        self.tbrMain.addSeparator()

        # 图像变换
        self.tbrMain.addAction(self.actBlackWhite)
        self.tbrMain.addAction(self.actMultiColour)
        self.tbrMain.addSeparator()
        self.tbrMain.addAction(self.actHorizontal)
        self.tbrMain.addAction(self.actVertical)
        self.tbrMain.addAction(self.actRotateLeft)
        self.tbrMain.addAction(self.actRotateRight)
        self.tbrMain.addAction(self.actZoomIn)
        self.tbrMain.addAction(self.actZoomOut)
        self.tbrMain.addSeparator()

        # 对比度、饱和度、亮度、清晰度
        self.tbrPanel.layout().setContentsMargins(20, 20, 20, 20)
        self.tbrPanel.layout().setSpacing(10)
        self.tbrPanel.setVisible(False)

        def add_panel_action(label, min_value, max_value, def_value, on_changed):
            label = QLabel(label)
            label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
            slider = QSlider()
            slider.setRange(min_value, max_value)
            slider.setValue(def_value)
            slider.setOrientation(Qt.Orientation.Horizontal)
            slider.valueChanged[int].connect(on_changed)
            self.tbrPanel.addWidget(label)
            self.tbrPanel.addWidget(slider)
            return slider

        self.sldContrast = add_panel_action('对 比 度', 20, 360, 100, self.contrast_changed)
        self.sldColor = add_panel_action('饱 和 度', 10, 360, 100, self.color_changed)
        self.sldBright = add_panel_action('亮    度', 20, 180, 100, self.bright_changed)
        self.sldSharp = add_panel_action('清 晰 度', 0, 360, 100, self.sharp_changed)

        # 合成模式
        self.cobCompose.addItems(['透明差值', 'R 通道', 'G 通道', 'B 通道'])
        self.cobCompose.setFont(QFont('仿宋', 14))
        self.cobCompose.setFixedSize(100, 32)
        self.cobCompose.setToolTip('合成方式')
        self.tbrCompose.addWidget(self.cobCompose)
        self.pbCompose.setIcon(QIcon('image/compose.jpg'))
        self.pbCompose.setIconSize(QSize(36, 36))
        self.pbCompose.setStyleSheet('background-color: whitesmoke;')
        self.pbCompose.setToolTip('开始合成')
        self.pbCompose.clicked.connect(self.compose_pic)
        self.tbrCompose.addWidget(self.pbCompose)
        self.tbrCompose.setVisible(False)

        # 人脸处理
        self.tbrFilter.layout().setContentsMargins(5, 5, 5, 5)
        self.tbrFilter.layout().setSpacing(10)

        def add_face_action(icon, tooltip, action):
            button = QPushButton(icon, None)
            button.setIconSize(QSize(128, 128))
            button.setToolTip(tooltip)
            button.setStyleSheet("background-color: whitesmoke;")
            button.clicked.connect(lambda: self.process_face(action))
            self.tbrFilter.addWidget(button)

        add_face_action(QIcon('image/blur.jpg'), '模糊', 'Blur')
        add_face_action(QIcon('image/detail.jpg'), '清晰', 'Detail')
        add_face_action(QIcon('image/contour.jpg'), '素描', 'Contour')
        add_face_action(QIcon('image/emboss.jpg'), '浮雕', 'Emboss')
        add_face_action(QIcon('image/mosaic.jpg'), '马赛克', 'Mosaic')
        ok = QPushButton('确  定')
        ok.setMinimumSize(128, 36)
        ok.setFont(QFont('仿宋', 14))
        ok.setStyleSheet('background-color: whitesmoke;')
        ok.clicked.connect(self.accept_face)
        self.tbrFilter.addWidget(ok)
        cancel = QPushButton('取  消')
        cancel.setMinimumSize(128, 36)
        cancel.setFont(QFont('仿宋', 14))
        cancel.setStyleSheet('background-color: whitesmoke;')
        cancel.clicked.connect(self.cancel_face)
        self.tbrFilter.addWidget(cancel)
        self.tbrFilter.setVisible(False)
        self.tbrMain.addAction(self.actFace)

    def open_pic(self):
        """
        打开图片
        """
        global picPath
        picPath = QFileDialog.getOpenFileName(self, '打开图片', '/', '*.jpg *.jpeg *.png *.gif *.ico *.bmp')[0]
        if self.load_pic() and self.pImage is not None:
            self.show_pic()

    def save_pic(self):
        """
        保存图片
        """
        if self.pImage is not None:
            # 检查目录是否存在
            dirpath = abspath(join('.', 'picture'))
            makedirs(dirpath, exist_ok=True)
            # 保存到应用 picture 目录下
            filepath = abspath(join(dirpath, '%s.png') % basename(picPath).split('.')[0])
            self.pImage.save(filepath)
            # 在文件管理器中打开
            system("start explorer \"%s\"" % dirpath)

    def save_as_pic(self):
        """
        另存为
        """
        global picPath
        if self.pImage is not None:
            pixmap = self.lbPic.pixmap()
            if pixmap is not None:
                image_dir = join(picPath, pardir)
                base, ext = QFileDialog.getSaveFileName(self, '另存为', image_dir, '.png')
                if len(base) > 0:
                    self.pImage.save(base + ext)
                    system("start explorer %s" % abspath(join(base, pardir)))

    def toggle_region(self, shape):
        """
        切换选区
        """
        global regionMode
        if shape == '' or regionMode == shape:
            self.actCircle.setChecked(False)
            self.pbSelCircle.setFlat(False)
            self.pbSelCircle.setStyleSheet('background-color: whitesmoke;')
            self.actRect.setChecked(False)
            self.pbSelRect.setFlat(False)
            self.pbSelRect.setStyleSheet('background-color: whitesmoke;')
            self.lbPic.setCursor(Qt.CursorShape.ArrowCursor)
            regionMode = ''
        elif shape == 'rect':
            self.actCircle.setChecked(False)
            self.pbSelCircle.setFlat(False)
            self.pbSelCircle.setStyleSheet('background-color: whitesmoke;')
            self.actRect.setChecked(True)
            self.pbSelRect.setFlat(True)
            self.pbSelRect.setStyleSheet('background-color: whitesmoke; '
                                         'border: 1px solid black;')
            self.lbPic.setCursor(Qt.CursorShape.CrossCursor)
            regionMode = shape
        elif shape == 'circle':
            self.actCircle.setChecked(True)
            self.pbSelCircle.setFlat(True)
            self.pbSelCircle.setStyleSheet('background-color: whitesmoke; '
                                           'border: 1px solid black;')
            self.actRect.setChecked(False)
            self.pbSelRect.setFlat(False)
            self.pbSelRect.setStyleSheet('background-color: whitesmoke;')
            self.lbPic.setCursor(Qt.CursorShape.CrossCursor)
            regionMode = shape

    def cut(self):
        """
        剪裁
        """
        if self.pImage is not None and regionMode != '':
            p1, p2 = self.lbPic.selected_region()
            self.pImage = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                            p2.x() * self.scale, p2.y() * self.scale))
            self.toggle_region('')
            self.show_pic()

    def copy(self):
        """
        复制
        """
        if self.pImage is not None and regionMode != '':
            p1, p2 = self.lbPic.selected_region()
            self.copyImage = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                               p2.x() * self.scale, p2.y() * self.scale))
            self.toggle_region('')

    def paste(self):
        """
        粘贴
        """
        if self.pImage is not None and self.copyImage is not None:
            pw = self.p.x() + self.copyImage.width
            ph = self.p.y() + self.copyImage.height
            if pw < self.width and ph < self.height:
                self.paste_region(self.p, self.copyImage)
                self.toggle_region('')
                self.show_pic()

    def toggle_image_mode(self, mode):
        """
        设置图片模式
        :param mode: 图片模式，L 为黑白，RGBA 为真彩
        """
        if self.pImage is not None:
            self.pImage = self.pImage.convert(mode)
            self.show_pic()

    def fit_ratio(self):
        """
        设置比例
        """
        if self.pImage is not None:
            dialog = RatioDialog(self, self.pImage.width, self.pImage.height)
            if dialog.exec():
                w = dialog.pic_width
                h = dialog.pic_height
                self.pImage = self.pImage.resize((w, h))
                self.show_pic()
            dialog.destroy()

    def transpose(self, method):
        """
        翻转
        :param method: 旋转方法，`Image.Transpose` 枚举
        """
        if self.pImage is not None:
            self.pImage = self.pImage.transpose(method)
            self.show_pic()

    def rotate(self, angle):
        """
        旋转
        :param angle: 旋转角度
        """
        if self.pImage is not None:
            self.pImage = self.pImage.rotate(angle)
            self.show_pic()

    def scale_pic(self, factor):
        """
        缩放
        :param factor: 放大倍数
        :return:
        """
        if self.pImage is not None:
            self.pImage = self.pImage.resize((int(self.pImage.width * factor),
                                              int(self.pImage.height * factor)))
            self.show_pic()

    def add_water_mark(self):
        if self.pImage is not None:
            dialog = TextMarkDialog()
            if dialog.exec():
                # 创建水印图片
                mark = Image.new('RGBA', self.pImage.size, (255, 255, 255, 0))
                font = ImageFont.truetype('simkai.ttf', int(72 * self.scale))
                draw = ImageDraw.Draw(mark)
                text = dialog.leMark.text()
                draw.text((0, 0), text, font=font, fill='#FFFF00')
                alpha = mark.split()[3]
                alpha = ImageEnhance.Brightness(alpha).enhance(0.618)
                mark.putalpha(alpha)
                # 向原始图片绘制水印
                self.pImage = Image.alpha_composite(self.pImage.convert('RGBA'), mark)
                self.show_pic()

    def enhance_contrast(self):
        """
        增强对比度
        """
        if self.pImage is not None:
            if regionMode == '':
                # 处理全图
                self.pImage = ImageEnhance.Contrast(self.pImage).enhance(1.618)
                self.pImage = self.pImage.convert('RGBA')
                self.show_pic()
            else:
                # 处理选区
                p1, p2 = self.lbPic.selected_region()
                if regionMode == 'circle':
                    self.paste_region(p1, self.proc_circle_or_face('Contrast'))
                else:
                    sel = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                            p2.x() * self.scale, p2.y() * self.scale))
                    sel = ImageEnhance.Contrast(sel).enhance(1.618)
                    self.paste_region(p1, sel)
                self.toggle_region('')

    def enhance_color(self):
        """
        增强饱和度
        """
        if self.pImage is not None:
            if regionMode == '':
                # 处理全图
                self.pImage = ImageEnhance.Color(self.pImage).enhance(3.6)
                self.pImage = self.pImage.convert('RGBA')
                self.show_pic()
            else:
                # 处理选区
                p1, p2 = self.lbPic.selected_region()
                if regionMode == 'circle':
                    self.paste_region(p1, self.proc_circle_or_face('Color'))
                else:
                    sel = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                            p2.x() * self.scale, p2.y() * self.scale))
                    sel = ImageEnhance.Color(sel).enhance(3.6)
                    self.paste_region(p1, sel)
                self.toggle_region('')

    def enhance_bright(self):
        """
        增强亮度
        """
        if self.pImage is not None:
            if regionMode == '':
                # 处理全图
                self.pImage = ImageEnhance.Brightness(self.pImage).enhance(0.9)
                self.pImage = self.pImage.convert('RGBA')
                self.show_pic()
            else:
                # 处理选区
                p1, p2 = self.lbPic.selected_region()
                if regionMode == 'circle':
                    self.paste_region(p1, self.proc_circle_or_face('Bright'))
                else:
                    sel = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                            p2.x() * self.scale, p2.y() * self.scale))
                    sel = ImageEnhance.Brightness(sel).enhance(0.9)
                    self.paste_region(p1, sel)
                self.toggle_region('')

    def enhance_sharp(self):
        """
        增强清晰度
        """
        if self.pImage is not None:
            if regionMode == '':
                # 处理全图
                self.pImage = ImageEnhance.Sharpness(self.pImage).enhance(2.618)
                self.pImage = self.pImage.convert('RGBA')
                self.show_pic()
            else:
                # 处理选区
                p1, p2 = self.lbPic.selected_region()
                if regionMode == 'circle':
                    self.paste_region(p1, self.proc_circle_or_face('Sharp'))
                else:
                    sel = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                            p2.x() * self.scale, p2.y() * self.scale))
                    sel = ImageEnhance.Sharpness(sel).enhance(2.618)
                    self.paste_region(p1, sel)
                self.toggle_region('')

    def contrast_changed(self, value):
        """
        手动调整对比度
        :param value: 对比度
        """
        if self.enhanceEnabled:
            # 检查图片备份
            if self.enhanceType != 'contrast' or self.enhanceImage is None:
                self.enhanceType = 'contrast'
                self.enhanceImage = self.pImage
            # 复位其他数据
            self.enhanceEnabled = False
            self.sldColor.setValue(100)
            self.sldBright.setValue(100)
            self.sldSharp.setValue(100)
            self.enhanceEnabled = True
            # 修改图片
            self.pImage = ImageEnhance.Contrast(self.enhanceImage).enhance(value / 100)
            self.pImage = self.pImage.convert('RGBA')
            self.show_pic()

    def color_changed(self, value):
        """
        手动调整饱和度
        :param value: 饱和度
        """
        if self.enhanceEnabled:
            # 检查图片备份
            if self.enhanceType != 'color' or self.enhanceImage is None:
                self.enhanceType = 'color'
                self.enhanceImage = self.pImage
            # 复位其他数据
            self.enhanceEnabled = False
            self.sldContrast.setValue(100)
            self.sldBright.setValue(100)
            self.sldSharp.setValue(100)
            self.enhanceEnabled = True
            # 修改图片
            self.pImage = ImageEnhance.Color(self.enhanceImage).enhance(value / 100)
            self.pImage = self.pImage.convert('RGBA')
            self.show_pic()

    def bright_changed(self, value):
        """
        手动调整亮度
        :param value: 亮度
        """
        if self.enhanceEnabled:
            # 检查图片备份
            if self.enhanceType != 'bright' or self.enhanceImage is None:
                self.enhanceType = 'bright'
                self.enhanceImage = self.pImage
            # 复位其他数据
            self.enhanceEnabled = False
            self.sldContrast.setValue(100)
            self.sldColor.setValue(100)
            self.sldSharp.setValue(100)
            self.enhanceEnabled = True
            # 修改图片
            self.pImage = ImageEnhance.Brightness(self.enhanceImage).enhance(value / 100)
            self.pImage = self.pImage.convert('RGBA')
            self.show_pic()

    def sharp_changed(self, value):
        """
        手动调整清晰度
        :param value: 清晰度
        """
        if self.enhanceEnabled:
            # 检查图片备份
            if self.enhanceType != 'sharp' or self.enhanceImage is None:
                self.enhanceType = 'sharp'
                self.enhanceImage = self.pImage
            # 复位其他数据
            self.enhanceEnabled = False
            self.sldContrast.setValue(100)
            self.sldBright.setValue(100)
            self.sldColor.setValue(100)
            self.enhanceEnabled = True
            # 修改图片
            self.pImage = ImageEnhance.Sharpness(self.enhanceImage).enhance(value / 100)
            self.pImage = self.pImage.convert('RGBA')
            self.show_pic()

    def compose_begin(self):
        """
        进入图像合成模式
        """
        if self.pImage is not None:
            # 选择合成图片
            global picPath2
            picPath2 = QFileDialog.getOpenFileName(self, '打开图片', '/', '*.jpg *.jpeg *.png *.gif *.ico *.bmp')[0]
            if picPath2 != '':
                self.cImage = Image.open(picPath2)
                self.cImage = self.cImage.resize(self.pImage.size)
                self.cImage = self.cImage.convert('RGBA')
                w = int(self.width / 2)
                h = int(self.height / 2)
                self.lbPic.setFixedSize(w, h)
                self.lbPic2.setGeometry(w, 0, w, h)
                self.lbPic2.setFixedSize(w, h)
                self.lbPic2.setPixmap(QPixmap(picPath2))
                self.lbPic2.setVisible(True)
                self.tbrCompose.setVisible(True)

    def compose_pic(self):
        """
        开始图像合成
        """
        if self.pImage is not None and self.cImage is not None:
            # 图像合成
            if self.cobCompose.currentText() == '透明差值':
                self.pImage = Image.blend(self.pImage, self.cImage, 0.618)
            else:
                self.pImage = self.pImage.convert('RGBA')
                r, g, b, _ = self.cImage.split()
                if self.cobCompose.currentText() == 'R 通道':
                    self.pImage = Image.composite(self.pImage, self.cImage, r)
                elif self.cobCompose.currentText() == 'G 通道':
                    self.pImage = Image.composite(self.pImage, self.cImage, g)
                elif self.cobCompose.currentText() == 'B 通道':
                    self.pImage = Image.composite(self.pImage, self.cImage, b)
            self.show_pic()
            # 恢复环境
            self.lbPic2.setVisible(False)
            self.cImage.close()
            self.cImage = None
            self.tbrCompose.setVisible(False)

    def face_filter(self):
        global regionMode
        if self.pImage is not None:
            cvimage = cv2.cvtColor(numpy.asarray(self.pImage), cv2.COLOR_BGR2RGB)
            detector = cv2.CascadeClassifier('detector/haarcascade_frontalface_alt.xml')
            faces = detector.detectMultiScale(cvimage, scaleFactor=1.19, minNeighbors=5)
            if len(faces) > 0:
                self.faces = []
                self.faceProcess = 0
                self.tbrFilter.setVisible(True)
                regionMode = 'face'
                self.fImage = self.pImage
                for (x, y, w, h) in faces:
                    # 描绘人脸
                    cvimage = cv2.rectangle(cvimage, (x, y), (x + w, y + h), (0, 255, 0), 2)
                    # 记录位置
                    self.faces.append((x, y, w / 2))
                self.pImage = Image.fromarray(cv2.cvtColor(cvimage, cv2.COLOR_BGR2RGB))
                self.show_pic()
            else:
                QMessageBox.critical(self, '人脸', '找不到人脸')

    def process_face(self, mode):
        if self.fImage is not None:
            self.pImage = self.fImage
            for i in range(len(self.faces)):
                self.faceProcess = i
                sel = self.proc_circle_or_face(mode)
                p = QPointF(self.faces[i][0] / self.scale, self.faces[i][1] / self.scale)
                self.paste_region(p, sel, False)
            self.show_pic()

    def accept_face(self):
        """
        接受人脸修改，实际只是清理环境
        """
        global regionMode
        regionMode = ''
        self.fImage = None
        self.tbrFilter.setVisible(False)

    def cancel_face(self):
        """
        取消人脸
        """
        if self.fImage is not None:
            self.pImage = self.fImage
            self.show_pic()
        self.accept_face()

    def proc_circle_or_face(self, mode):
        """
        美化图像 - 处理圆形选区
        :param mode: Contrast, Color, Bright, Sharp, Blur, Detail, Contour, Emboss, Mosaic
        :return 处理后的选区图片
        """
        if self.pImage is None or (regionMode != 'circle' and regionMode != 'face'):
            return

        if regionMode == 'circle':
            p1, p2 = self.lbPic.selected_region()
            sel = self.pImage.crop((p1.x() * self.scale, p1.y() * self.scale,
                                    p2.x() * self.scale, p2.y() * self.scale))
            r = int(sel.width / 2)
        else:
            x, y, r = self.faces[self.faceProcess]
            sel = self.pImage.crop((x, y, x + r * 2, y + r * 2))

        srcImage = sel
        srcMatrix = srcImage.load()
        tarImage = None
        if mode == 'Contrast':
            # 饱和度
            tarImage = ImageEnhance.Contrast(sel).enhance(1.618)
        elif mode == 'Color':
            # 饱和度
            tarImage = ImageEnhance.Color(sel).enhance(3.6)
        elif mode == 'Bright':
            # 亮度
            tarImage = ImageEnhance.Brightness(sel).enhance(0.9)
        elif mode == 'Sharp':
            # 清晰度
            tarImage = ImageEnhance.Sharpness(sel).enhance(2.618)
        elif mode == 'Blur':
            # 模糊（人脸）
            tarImage = sel.filter(ImageFilter.BLUR).filter(ImageFilter.SMOOTH)
        elif mode == 'Detail':
            # 清晰（人脸）
            tarImage = sel.filter(ImageFilter.DETAIL).filter(ImageFilter.SHARPEN)
        elif mode == 'Contour':
            # 素描（人脸）
            tarImage = sel.filter(ImageFilter.CONTOUR)
        elif mode == 'Emboss':
            # 浮雕（人脸）
            tarImage = sel.filter(ImageFilter.EMBOSS)
        elif mode == 'Mosaic':
            size = sel.size
            # 马赛克：先缩小，再放大，再分格
            tarImage = sel.resize((26, 26)).resize(size).filter(ImageFilter.EDGE_ENHANCE)

        tarMatrix = tarImage.load()
        retImage = Image.new('RGBA', sel.size, (255, 255, 255, 0))
        retMatrix = retImage.load()
        for i in range(sel.width):
            for j in range(sel.height):
                dx = i - r
                dy = j - r
                d = (pow(dx, 2) + pow(dy, 2)) ** 0.5
                if d <= r:
                    retMatrix[i, j] = tarMatrix[i, j]
                else:
                    retMatrix[i, j] = srcMatrix[i, j]
        return retImage

    def paste_region(self, p, image, show = True):
        """
        粘贴选区
        :param p: 选区左上角
        :param image: 选区图片
        :return:
        """
        region = (p.x() * self.scale, p.y() * self.scale,
                  p.x() * self.scale + image.width, p.y() * self.scale + image.height)
        self.pImage.paste(image, tuple(int(v) for v in region))
        self.pImage = self.pImage.convert('RGBA')
        if show:
            self.show_pic()

    def update_status(self, point):
        """
        获取鼠标位置
        :param point: 鼠标位置
        """
        if regionMode == '':
            self.p = point
        pos = point.toPoint()
        self.statusbar.showMessage('(%.4f, %.4f)' % (pos.x() * self.scale, pos.y() * self.scale))

    def load_pic(self):
        """
        加载图片，将图片存入 pImage
        :return 是否成功加载
        """
        global picPath
        if picPath != '':
            self.pImage = Image.open(picPath)
            return True
        else:
            return False

    def show_pic(self, mw=1024, mh=768):
        """
        自适应显示图片，缩放到给定区间
        :param mw: 图片显示区域宽度
        :param mh: 图片显示区域高度
        """
        w = self.pImage.width
        h = self.pImage.height
        origin_width = w
        # 缩放适应
        ratio = w / h
        if w > mw and (h <= mh or mh < h < w):
            w = mw
            h = w / ratio
        elif (w <= mw and h > mh) or (mw < w <= h and h > mh):
            h = mh
            w = h * ratio

        self.width = w
        self.height = h
        self.scale = origin_width / self.width
        self.lbPic.setFixedSize(int(w), int(h))
        self.lbPic.setScaledContents(True)
        # convert to QPixmap
        bytes = BytesIO()
        try:
            self.pImage.save(bytes, 'PNG')
        except Exception as e:
            # 加载失败
            print(e)
            self.pImage = None
            QMessageBox.critical(self, '错误', str(e))
            return
        pixmap = QPixmap()
        pixmap.loadFromData(bytes.getvalue())
        self.lbPic.setPixmap(pixmap)

    def region_mode(self):
        """
        返回当前选区类型
        :return: 选区类型
        """
        return regionMode

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = MyPicShop()
    mainWindow.show()
    sys.exit(app.exec())
